package cn.piggy.gateway.config.config;

import cn.piggy.gateway.config.application.GatewayApplication;
import cn.piggy.gateway.config.domain.service.GatewayCenterService;
import cn.piggy.gateway.core.config.ConfigHelper;
import cn.piggy.gateway.core.limiter.RedisLimiter;
import cn.piggy.gateway.core.session.simple.SimpleGatewaySessionFactory;
import cn.piggy.gateway.core.socket.GatewaySocketServer;
import io.netty.channel.Channel;
import org.apache.curator.RetryPolicy;
import org.apache.curator.framework.CuratorFramework;
import org.apache.curator.framework.CuratorFrameworkFactory;
import org.apache.curator.framework.recipes.cache.ChildData;
import org.apache.curator.framework.recipes.cache.TreeCache;
import org.apache.curator.framework.recipes.cache.TreeCacheEvent;
import org.apache.curator.framework.recipes.cache.TreeCacheListener;
import org.apache.curator.retry.ExponentialBackoffRetry;
import org.redisson.api.RedissonClient;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.context.properties.EnableConfigurationProperties;
import org.springframework.context.ApplicationContext;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.data.redis.connection.RedisConnectionFactory;
import org.springframework.data.redis.connection.RedisStandaloneConfiguration;
import org.springframework.data.redis.connection.jedis.JedisClientConfiguration;
import org.springframework.data.redis.connection.jedis.JedisConnectionFactory;
import org.springframework.data.redis.listener.PatternTopic;
import org.springframework.data.redis.listener.RedisMessageListenerContainer;
import org.springframework.data.redis.listener.adapter.MessageListenerAdapter;
import org.springframework.lang.NonNull;
import redis.clients.jedis.JedisPoolConfig;
import java.time.Duration;
import java.util.Map;
import java.util.concurrent.ExecutionException;
import java.util.concurrent.FutureTask;


@Configuration
@EnableConfigurationProperties(GatewayServiceProperties.class)
public class GatewayAutoConfig {

    private final Logger logger = LoggerFactory.getLogger(GatewayAutoConfig.class);

    @Bean
    public GatewayCenterService registerGatewayService() {
        return new GatewayCenterService();
    }

    @Bean
    public RedissonClient redissonClient(GatewayServiceProperties properties, @NonNull GatewayCenterService gatewayCenterService){
        // 1. 拉取注册中心的 Redis 配置信息
        Map<String, String> redisConfig = gatewayCenterService.queryRedisConfig(properties.getInterfaceAddress());
        // 2. 构建 Redisson 服务
        RedissonClient redissonClient = RedisLimiter.redisInit("redis://" + redisConfig.get("host") + ":" + redisConfig.get("port"), redisConfig.get("password"));
        logger.info("redisson init finish ...");
        return redissonClient;
    }

    @Bean
    public RedisConnectionFactory redisConnectionFactory(GatewayServiceProperties properties, @NonNull GatewayCenterService gatewayCenterService) {
        // 1. 拉取注册中心的 Redis 配置信息
        Map<String, String> redisConfig = gatewayCenterService.queryRedisConfig(properties.getInterfaceAddress());
        // 2. 构建 Redis 服务
        RedisStandaloneConfiguration standaloneConfig = new RedisStandaloneConfiguration();
        standaloneConfig.setHostName(redisConfig.get("host"));
        standaloneConfig.setPort(Integer.parseInt(redisConfig.get("port")));
        standaloneConfig.setPassword(redisConfig.get("password"));
        // 3. 默认配置信息；一般这些配置可以被抽取出来
        JedisPoolConfig poolConfig = new JedisPoolConfig();
        poolConfig.setMaxTotal(100);
        poolConfig.setMaxWaitMillis(30 * 1000);
        poolConfig.setMinIdle(20);
        poolConfig.setMaxIdle(40);
        poolConfig.setTestWhileIdle(true);
        // 4. 创建 Redis 配置
        JedisClientConfiguration clientConfig = JedisClientConfiguration.builder()
                .connectTimeout(Duration.ofSeconds(2))
                .clientName("api-gateway-redis-" + properties.getGatewayId())
                .usePooling().poolConfig(poolConfig).build();
        // 5. 实例化 Redis 链接对象
        return new JedisConnectionFactory(standaloneConfig, clientConfig);
    }
    /**
     * 创建网关配置对象；Configuration 是用于贯穿整个网关核心通信服务的。
     */
    @Bean
    public ConfigHelper gatewayCoreConfiguration(GatewayServiceProperties properties) {
        ConfigHelper configuration = new ConfigHelper();
        String[] split = properties.getGatewayAddress().split(":");
        configuration.setHostName(split[0].trim());
        configuration.setPort(Integer.parseInt(split[1].trim()));
        return configuration;
    }

    /**
     * 初始化网关服务；创建服务端 Channel 对象，方便获取和控制网关操作。
     */
    @Bean("gatewaySocketServerChannel")
    public Channel initGateway(ConfigHelper configuration) throws ExecutionException, InterruptedException {
        // 1. 基于配置构建会话工厂
        SimpleGatewaySessionFactory gatewaySessionFactory = new SimpleGatewaySessionFactory(configuration);
        // 2. 创建启动网关网络服务
        GatewaySocketServer server = new GatewaySocketServer(configuration, gatewaySessionFactory);
//        Future<Channel> future = Executors.newSingleThreadExecutor().submit(server);
        FutureTask<Channel> future = new FutureTask<>(server);
        Thread thread = new Thread(future);
        thread.start();
        Channel channel = future.get();
        if (null == channel)
            throw new RuntimeException("api gateway core netty server start error channel is null");

        for(int i=0; i<5; i++){
            if(!channel.isActive()){
                logger.info("api gateway core netty server gateway start Ing ...");
                Thread.sleep(1000);
            }
        }
        if(!channel.isActive()){
            throw new RuntimeException("http channel is not active");
        }

        logger.info("api gateway core netty server gateway start Done! {}", channel.localAddress());
        return channel;
    }
    @Bean
    public GatewayApplication gatewayApplication(GatewayServiceProperties properties, GatewayCenterService registerGatewayService,
                                                 ConfigHelper configuration, Channel gatewaySocketServerChannel) {
        return new GatewayApplication(properties, registerGatewayService, configuration, gatewaySocketServerChannel);
    }

    // GatewayApplication 声明监听方法
    @Bean
    public MessageListenerAdapter msgAgreementListenerAdapter(GatewayApplication gatewayApplication) {
        return new MessageListenerAdapter(gatewayApplication, "receiveMessage");
    }

    @Bean
    public RedisMessageListenerContainer container(GatewayServiceProperties properties, RedisConnectionFactory redisConnectionFactory,
                                                   @Autowired MessageListenerAdapter msgAgreementListenerAdapter) {
        RedisMessageListenerContainer container = new RedisMessageListenerContainer();
        container.setConnectionFactory(redisConnectionFactory);
        // topic --> properties.getGatewayId()
        container.addMessageListener(msgAgreementListenerAdapter, new PatternTopic(properties.getGatewayId()));
        return container;
    }

    @Bean(initMethod = "start")
    public CuratorFramework curatorFramework(GatewayServiceProperties properties, GatewayApplication gatewayApplication) throws Exception {
        // todo 配置参数修改
        RetryPolicy retryPolicy = new ExponentialBackoffRetry(3000, 3);
        CuratorFramework build = CuratorFrameworkFactory.builder()
                .connectString(properties.getZookeeperAddress()).retryPolicy(retryPolicy)
                .build();

        TreeCache treeCache = new TreeCache(build,"/"+properties.getGatewayId());
        treeCache.getListenable().addListener(new TreeCacheListener() {
            @Override
            public void childEvent(CuratorFramework curatorFramework, TreeCacheEvent treeCacheEvent) {
                ApplicationContext applicationContext = gatewayApplication.getApplicationContext();
                ConfigHelper bean = applicationContext.getBean(ConfigHelper.class);
                ChildData data = treeCacheEvent.getData();
                if(data!=null){
                    String addr = new String(data.getData());
                    if(!"".equals(addr)){
                        String path = data.getPath();
                        String[] split = path.split("/");
                        switch (treeCacheEvent.getType()) {
                            case NODE_ADDED:
                                bean.registryAddr(split[2],addr);
                                break;
                            case NODE_REMOVED:
                                bean.deleteAddr(split[2],addr);
                                break;
                        }
                    }
                }
            }
        });
        treeCache.start();
        return build;
    }


}
